package com.ddxz.tool.core.common;

import com.ddxz.tool.core.convert.DateUtil;
import com.ddxz.tool.core.crypto.SecureUtil;
import com.ddxz.tool.core.exception.DdxzBaseException;
import com.ddxz.tool.core.security.SecurityUtil;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;

import static com.ddxz.tool.core.exception.DdxzErrorStatus.*;

/**
 * @author 朱宗刚
 * @version 1.0.0
 * @description com.casic.exchange.tools.convert.CallUtil
 * @date 2020/2/28
 */
public class CommonUtil {
    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = DateUtil.getTimestamp();
        while (timestamp <= lastTimestamp) {
            timestamp = DateUtil.getTimestamp();
        }
        return timestamp;
    }

    public static Long snowId() {
        long timestamp = DateUtil.getTimestamp();

        //获取当前时间戳如果小于上次时间戳，则表示时间戳获取出现异常
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        //获取当前时间戳如果等于上次时间戳（同一毫秒内），则在序列号加一；否则序列号赋值为0，从0开始。
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        //将上次时间戳值刷新
        lastTimestamp = timestamp;

        /**
         * 返回结果：
         * (timestamp - twepoch) << timestampLeftShift) 表示将时间戳减去初始时间戳，再左移相应位数
         * (datacenterId << datacenterIdShift) 表示将数据id左移相应位数
         * (workerId << workerIdShift) 表示将工作id左移相应位数
         * | 是按位或运算符，例如：x | y，只有当x，y都为0的时候结果才为0，其它情况结果都为1。
         * 因为个部分只有相应位上的值有意义，其它位上都是0，所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        return ((timestamp - 1234567890L) << timestampLeftShift) |
                (workId << workerIdShift) |
                sequence;
    }

    public static <T> T getInstance(Class<T> tar) {
        try{
            Constructor<T> constructor = tar.getConstructor();
            constructor.setAccessible(true);
            return constructor.newInstance();
        } catch (Exception e) {
            throw new DdxzBaseException(e, CONSTRUCTOR_NOT_FOUND);
        }
    }

    public static String randStr() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    public static void ddxzAssetNull(Object obj, DdxzStatus status, Object... args) {
        if(null == obj) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static void ddxzAssetLess(Comparable left, Comparable right, DdxzStatus status, Object... args) {
        ddxzAssetNull(left, OBJECT_IS_NULL);
        ddxzAssetNull(right, OBJECT_IS_NULL);
        if(left.compareTo(right) < 0) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static void ddxzAssetUnless(Comparable left, Comparable right, DdxzStatus status, Object... args) {
        ddxzAssetNull(left, OBJECT_IS_NULL);
        ddxzAssetNull(right, OBJECT_IS_NULL);
        if(left.compareTo(right) >= 0) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static void ddxzAssetGreat(Comparable left, Comparable right, DdxzStatus status, Object... args) {
        ddxzAssetNull(left, OBJECT_IS_NULL);
        ddxzAssetNull(right, OBJECT_IS_NULL);
        if(left.compareTo(right) > 0) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static void ddxzAssetUngreat(Comparable left, Comparable right, DdxzStatus status, Object... args) {
        ddxzAssetNull(left, OBJECT_IS_NULL);
        ddxzAssetNull(right, OBJECT_IS_NULL);
        if(left.compareTo(right) <= 0) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static void ddxzAssetEqual(Comparable left, Comparable right, DdxzStatus status, Object... args) {
        ddxzAssetNull(left, OBJECT_IS_NULL);
        ddxzAssetNull(right, OBJECT_IS_NULL);
        if(left.equals(right)) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static void ddxzAssetUnequal(Comparable left, Comparable right, DdxzStatus status, Object... args) {
        ddxzAssetNull(left, OBJECT_IS_NULL);
        ddxzAssetNull(right, OBJECT_IS_NULL);
        if(left.compareTo(right) != 0) {
            throw new DdxzBaseException(status, args);
        }
    }

    public static String generatorId(String relName) {
        return SixtyTwo.transform(relName).toString() + '_' + randStr();
    }

    public static String generatorId(String relName, String key) {
        return SixtyTwo.transform(relName).toString() + '_' + SecureUtil.sha1(key);
    }

    public static byte[] randomBytes(int length) {
        byte[] bytes = new byte[length];
        ThreadLocalRandom.current().nextBytes(bytes);
        return bytes;
    }

    public static String random(int size) {
        if(size <= 0) {
            return "";
        }

        StringBuilder builder = new StringBuilder();
        for(int i = 0; i < size; ++i) {
            builder.append(CHARS[CommonUtil.randInt(CHARS.length)]);
        }

        return builder.toString();
    }

    public static Integer randInt() {
        return Math.abs(RANDOM.nextInt());
    }

    public static Integer randInt(int max) {
        return RANDOM.nextInt(max);
    }

    public static Integer randInt(int min, int max) {
        if(max < min) {
            throw new DdxzBaseException(MAX_LESS_MIN, max, min);
        }

        return min + RANDOM.nextInt(max - min);
    }

    public static Long randLong() {
        return Math.abs(RANDOM.nextLong());
    }

    public static Long randLong(Long max) {
        if(max < Integer.MAX_VALUE) {
            return randInt(max.intValue()).longValue();
        }

        long rst;
        do{
            rst = RANDOM.nextLong();
        } while (rst < 0 || rst >= max);

        return rst;
    }

    public static Long randLong(long min, long max) {
        if(max < min) {
            throw new DdxzBaseException(MAX_LESS_MIN, max, min);
        }

        return min + randLong(max - min);
    }

    public static Float randFloat() {
        return RANDOM.nextFloat();
    }

    public static Float randFloat(float max) {
        return max * RANDOM.nextFloat();
    }

    public static Float randFloat(float min, float max) {
        if(max < min) {
            throw new DdxzBaseException(MAX_LESS_MIN, max, min);
        }
        return min + randFloat(max - min);
    }

    public static Double randDouble() {
        return RANDOM.nextDouble();
    }

    public static Double randDouble(double max) {
        return max * RANDOM.nextDouble();
    }

    public static Double randDouble(double min, double max) {
        return min + randDouble(max - min);
    }

    public static Double randGauss() {
        return RANDOM.nextGaussian();
    }

    public static Double randGauss(double dev) {
        return RANDOM.nextGaussian() * dev;
    }

    private static final Set<String> ADMINS = new HashSet<>();
    private static Long lastTimestamp = DateUtil.getTimestamp();
    private static final Set<String> ADMIN_LIST = new HashSet<>();
    //最大值
    private static final Long maxWorkerId = ~(-1L << 6L);
    //序列号最大值
    private static final Long sequenceMask = ~(-1L << 10L);
    //工作id需要左移的位数，10位
    private static final Long workerIdShift = 10L;
    //时间戳需要左移位数 10+6+0=22位
    private static final Long timestampLeftShift = 22L;
    private static final Random  RANDOM = new Random(DateUtil.getTimestamp());
    private static final Long workId = randInt(maxWorkerId.intValue()).longValue();

    private static Long sequence = 0L;

    private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z'};

    static {
        ADMINS.add("admin");
        ADMIN_LIST.add("ADMIN");
    }
}
